package com.harry.configuration.poentity;

import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.apache.commons.lang3.ArrayUtils;

import com.harry.basis.BasePoEntity;
import com.harry.decoupling.defi.proxy.GenericSrvProxy;
import com.harry.utility.BeanCtxUtils;
import com.harry.utility.InnerUtils;
import com.harry.utility.PoEntityHelper;
import com.harry.utility.query.InnerCriteria;

public class PoVali {

	private final List<String> valis = new LinkedList<>();

	public String[] build() {
		return valis.toArray(ArrayUtils.toArray());
	}

	public PoVali notNull() {
		valis.add("notNull");
		return this;
	}

	public PoVali unique() {
		valis.add("unique");
		return this;
	}

	// string
	public PoVali maxLen(Integer max) {
		valis.add(String.format("maxLen(%s)", max));
		return this;
	}

	// collection / array
	public PoVali maxSize(Integer max) {
		valis.add(String.format("maxSize(%s)", max));
		return this;
	}

	// number
	public PoVali maxNmbr(Number max) {
		valis.add(String.format("maxNmbr(%s)", max));
		return this;
	}

	// date
	public PoVali maxDate(Date max) {
		valis.add(String.format("maxDate(%s)", max.getTime()));
		return this;
	}

	// string
	public PoVali minLen(Integer min) {
		valis.add(String.format("minLen(%s)", min));
		return this;
	}

	// collection / array
	public PoVali minSize(Integer min) {
		valis.add(String.format("minSize(%s)", min));
		return this;
	}

	// number
	public PoVali minNmbr(Number min) {
		valis.add(String.format("minNmbr(%s)", min));
		return this;
	}

	// date
	public PoVali minDate(Date min) {
		valis.add(String.format("minDate(%s)", min.getTime()));
		return this;
	}

	public PoVali email() {
		valis.add("email");
		return this;
	}

	public PoVali password() {
		valis.add("pswd");
		return this;
	}

	public PoVali confirmPassword() {
		valis.add("confimPswd");
		return this;
	}

	public PoVali cron() {
		valis.add("cron");
		return this;
	}

	private static Map<String, PoValidator> type_povalidate_map = new HashMap<>();

	public static PoValidator findValidator(String type) {
		return type_povalidate_map.get(type);
	}

	public static interface PoValidator {

		void validate(Class<? extends BasePoEntity> clazz, String fieldName, String arg, Object data) throws Exception;
	}

	final static String cronExp = "^(((((((\\*)|(([1-5]?[0-9])(,([1-5]?[0-9]))*)|(([1-5]?[0-9])(\\-([1-5]?[0-9]))?(\\/\\d+)?))\\s((\\*)|(([1-5]?[0-9])(,([1-5]?[0-9]))*)|(([1-5]?[0-9])(\\-([1-5]?[0-9]))?(\\/\\d+)?)))\\s((\\*)|(((1?[0-9])|(2[0-3]))(,((1?[0-9])|(2[0-3])))*)|(((1?[0-9])|(2[0-3]))(\\-((1?[0-9])|(2[0-3])))?(\\/\\d+)?)))\\s((\\*)|(([1-9]|([1-2][0-9])|(3[0-1]))(,([1-9]|([1-2][0-9])|(3[0-1])))*)|(([1-9]|([1-2][0-9])|(3[0-1]))(\\-([1-9]|([1-2][0-9])|(3[0-1])))?(\\/\\d+)?)|(\\?)|(([1-9]|([1-2][0-9])|(3[0-1]))W)(([1-9]|([1-2][0-9])|(3[0-1]))L)(([1-9]|([1-2][0-9])|(3[0-1]))C)|(([1-9]|([1-2][0-9])|(3[0-1]))W)))\\s((\\*)|(([1-9]|(1[0-2]))(,([1-9]|(1[0-2])))*)|(([1-9]|(1[0-2]))(\\-([1-9]|(1[0-2])))?(\\/\\d+)?)))\\s((\\*)|([1-7](,[1-7])*)|([1-7](\\-[1-7])?(\\/\\d+)?)|(\\?)|([1-7]W)([1-7]L)([1-7]C)|([1-7]#[1-4]))(\\s((\\*)|(((19[7-9][0-9])|(20[0-9][0-9]))(,((19[7-9][0-9])|(20[0-9][0-9])))*)|(((19[7-9][0-9])|(20[0-9][0-9]))(\\-((19[7-9][0-9])|(20[0-9][0-9])))?(\\/\\d+)?)))?)$";
	static {
		type_povalidate_map.put("notNull", (clazz, fieldName, arg, data) -> {
			Object value = PoEntityHelper.readValueByProperty(data, fieldName);
			Object id = PoEntityHelper.readValueByProperty(data, "id");
			boolean able = id == null ? PoEntityHelper.isInsertable(clazz, fieldName) : PoEntityHelper.isUpdatable(clazz, fieldName);
			if (able && (value == null || "".equals(value))) throw new Exception(clazz.getSimpleName() + "." + fieldName + " cannot be empty.");
		});
		type_povalidate_map.put("unique", (clazz, fieldName, arg, data) -> {
			Object value = PoEntityHelper.readValueByProperty(data, fieldName);
			GenericSrvProxy srv = BeanCtxUtils.getBeanByType(GenericSrvProxy.class);
			if (srv.countByCriteria(clazz, new InnerCriteria("eq", "id", PoEntityHelper.readValueByProperty(data, "id"), "ne", fieldName, value)) > 0) {
				throw new Exception("Duplicate " + fieldName + " " + value);
			}
		});
		type_povalidate_map.put("maxLen", (clazz, fieldName, arg, data) -> {
			String value = (String) PoEntityHelper.readValueByProperty(data, fieldName);
			int length = value == null ? 0 : value.length();
			if (length > Integer.parseInt(arg, 10)) {
				throw new Exception("The length of " + clazz.getSimpleName() + "." + fieldName + " cannot be longer than " + arg);
			}
		});
		type_povalidate_map.put("maxSize", (clazz, fieldName, arg, data) -> {
			Object value = PoEntityHelper.readValueByProperty(data, fieldName);
			int length = PoEntityHelper.toObjectArray(value).length;
			if (length > Integer.parseInt(arg, 10)) {
				throw new Exception("The size of " + clazz.getSimpleName() + "." + fieldName + " cannot be larger than " + arg);
			}
		});
		type_povalidate_map.put("maxNmbr", (clazz, fieldName, arg, data) -> {
			int value = Number.class.cast(PoEntityHelper.readValueByProperty(data, fieldName)).intValue();
			if (value > Integer.parseInt(arg, 10)) {
				throw new Exception("The value of " + clazz.getSimpleName() + "." + fieldName + " cannot be larger than " + arg);
			}
		});
		type_povalidate_map.put("maxDate", (clazz, fieldName, arg, data) -> {
			long value = Number.class.cast(PoEntityHelper.readValueByProperty(data, fieldName)).longValue();
			if (Long.compare(value, Long.parseLong(arg, 10)) > 0) {
				throw new Exception("The value of " + clazz.getSimpleName() + "." + fieldName + " cannot be larger than " + InnerUtils.dateFormat().format(new Date(Long.parseLong(arg, 10))));
			}
		});
		type_povalidate_map.put("minLen", (clazz, fieldName, arg, data) -> {
			String value = (String) PoEntityHelper.readValueByProperty(data, fieldName);
			int length = value == null ? 0 : value.length();
			if (length < Integer.parseInt(arg, 10)) {
				throw new Exception("The length of " + clazz.getSimpleName() + "." + fieldName + " cannot be shorter than " + arg);
			}
		});
		type_povalidate_map.put("minSize", (clazz, fieldName, arg, data) -> {
			Object value = PoEntityHelper.readValueByProperty(data, fieldName);
			int length = PoEntityHelper.toObjectArray(value).length;
			if (length < Integer.parseInt(arg, 10)) {
				throw new Exception("The size of " + clazz.getSimpleName() + "." + fieldName + " cannot be smaller than " + arg);
			}
		});
		type_povalidate_map.put("minNmbr", (clazz, fieldName, arg, data) -> {
			int value = Number.class.cast(PoEntityHelper.readValueByProperty(data, fieldName)).intValue();
			if (value < Integer.parseInt(arg, 10)) {
				throw new Exception("The value of " + clazz.getSimpleName() + "." + fieldName + " cannot be smaller than " + arg);
			}
		});
		type_povalidate_map.put("minDate", (clazz, fieldName, arg, data) -> {
			long value = Number.class.cast(PoEntityHelper.readValueByProperty(data, fieldName)).longValue();
			if (Long.compare(value, Long.parseLong(arg, 10)) > 0) {
				throw new Exception("The time of " + clazz.getSimpleName() + "." + fieldName + " cannot be earlier than " + InnerUtils.dateFormat().format(new Date(Long.parseLong(arg, 10))));
			}
		});
		type_povalidate_map.put("email", (clazz, fieldName, arg, data) -> {
			String value = (String) PoEntityHelper.readValueByProperty(data, fieldName);
			if (!Pattern.matches("^\\w+([\\.-]?\\w+)*@\\w+([\\.-]?\\w+)*(\\.\\w{2,3})+$", value)) {
				throw new Exception(clazz.getSimpleName() + "." + fieldName + " is a invalid email address.");
			}
		});
		type_povalidate_map.put("pswd", (clazz, fieldName, arg, data) -> {
			String value = (String) PoEntityHelper.readValueByProperty(data, fieldName);
			if (!Pattern.matches(".+", value)) {
				throw new Exception(clazz.getSimpleName() + "." + fieldName + " is a invalid password.");
			}
		});
		type_povalidate_map.put("cron", (clazz, fieldName, arg, data) -> {
			String value = (String) PoEntityHelper.readValueByProperty(data, fieldName);
			if (!Pattern.matches(cronExp, value)) {
				throw new Exception(clazz.getSimpleName() + "." + fieldName + " is a invalid cron expression.");
			}
		});
	}
}
